<?php
namespace Tbf\Bundle\CronBundle;

use Doctrine\DBAL\LockMode;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Tbf\Component\DateTime\Clock\ClockInterface;
use Tbf\Bundle\CronBundle\Entity\Cron;
use Tbf\Bundle\CronBundle\Entity\CronRepository;
use Tbf\Bundle\CronBundle\Entity\PersistCron;

/**
 * 基于轮询模型的定时任务管理器
 * 如果执行完毕后,定时器上面的下一次时间比当前时间早,则将定时器上面的下一次时间修改到当前时间以后
 */
class CronManager implements EventSubscriberInterface
{
    /**
     * @var \Tbf\Bundle\CronBundle\Entity\CronRepository
     */
    protected $repository;
    protected $logger;
    protected $container;
    protected $clock;
    /** @var PersistCronInterface[] */
    protected $persist_cron_list = array();
    public function __construct(CronRepository $repository,
                         ContainerInterface $container,
                         LoggerInterface $logger,
                         ClockInterface $clock){
        $this->repository = $repository;
        $this->container = $container;
        $this->logger = $logger;
        $this->clock = $clock;
    }
    /**
     * 添加一个定时任务
     */
    public function addCron(Cron $cron)
    {
        $this->repository->persist($cron);
        $this->logger->debug('tbf.cron add Cron',array(
            'id'=>$cron->getId(),
            'next_time'=>$cron->getNextTime(),
            'task_class_name'=>get_class($cron->getTask())
        ));
    }

    /**
     * 被轮询的了接口
     * 需要锁定这个定时任务....
     * 在执行之前 提交事务
     * 1.读取可以运行的定时任务(这个不锁定)
     * 2.开始事务
     * 3.锁定这个定时任务(重新读取)
     * 4.是否有效,如果无效->删除->提交事务->退出
     * 5.如果有效->设置定时任务下次执行时间->下次是否有效->如果无效->删除->提交事务
     * 6.执行这个定时任务.
     */
    public function onTrigger()
    {
        $this->initPersistCron();
        $now = $this->clock->now();
        $cron_list = $this->repository->getNeedRunList($now);
        foreach ($cron_list as $cron) {
            $this->repository->getEm()->beginTransaction();
            try{
                $cron = $this->repository->find($cron->getId(),LockMode::PESSIMISTIC_WRITE);
                if (empty($cron)){ //可能被其他事务删除了
                    continue;
                }
                if (!$cron->isValid()) {
                    $this->removeCron($cron);
                    $this->repository->getEm()->commit();
                    continue;
                }
                $this->logger->debug('tbf.cron run Cron',array(
                    'id'=>$cron->getId(),
                    'next_time'=>$cron->getNextTime(),
                    'task_class_name'=>get_class($cron->getTask())
                ));
                $cron->moveTimeNext($now);
                if (!$cron->isValid()) {
                    $this->removeCron($cron);
                }else{
                    $this->repository->persist($cron);
                }
                $this->repository->getEm()->commit();
            }catch(\Exception $e){
                $this->repository->getEm()->rollback();
                $this->logger->error('CronManager catch an exception in onTrigger in database transaction',array(
                    'msg' => $e->getMessage(),
                    'trace'=>$e->getTraceAsString()
                ));
                continue;
            }
            try{
                $cron->run($this->container);
            }catch (\Exception $e){
                $this->logger->error('CronManager catch an exception in onTrigger in cron run',array(
                    'msg' => $e->getMessage(),
                    'trace'=>$e->getTraceAsString()
                ));
            }
        }
    }
    public function addPersistCron(PersistCronInterface $persist_cron)
    {
        $this->persist_cron_list[]= $persist_cron;
    }
    public static function getSubscribedEvents()
    {
        return array('job_trigger'=>'onTrigger');
    }
    protected function removeCron(Cron $cron)
    {
        $this->logger->debug('tbf.cron remove Cron',array(
            'id'=>$cron->getId(),
            'next_time'=>$cron->getNextTime(),
            'task_class_name'=>get_class($cron->getTask())
        ));
        $this->repository->remove($cron);
    }

    /**
     * 初始化persist_cron
     * 持久化计划任务,保证数据库一定有这个计划任务
     */
    public function initPersistCron()
    {
        foreach ($this->persist_cron_list as $persist_cron) {
            $name = $persist_cron->getName();
            $db_persist_cron = $this->repository->findPersistCronByName($name);
            if (!empty($db_persist_cron)) {
                continue;
            }
            $cron = $persist_cron->newCron();
            $this->repository->persist($cron);

            $db_persist_cron = new PersistCron($name,$cron);
            $this->repository->persist($db_persist_cron);

        }
    }
}
